﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Web;
using System.Workflow.Activities;
using System.Workflow.Runtime;
using PD.DataAccess;
using PD.WorkflowLibrary;

namespace PD.Web
{
    /// <summary>
    /// The module for maintaining the all workflows instances.
    /// </summary>
    public class MainModule : IHttpModule
    {
        /// <summary>
        /// The application context.
        /// </summary>
        private HttpApplication context;

        /// <summary>
        /// Disposes of the resources (other than memory) used by the module that implements <see cref="T:System.Web.IHttpModule"/>.
        /// </summary>
        public void Dispose()
        {
        }

        /// <summary>
        /// Initializes a module and prepares it to handle requests.
        /// </summary>
        /// <param name="context">An <see cref="T:System.Web.HttpApplication"/> that provides access to the methods, properties, and events common to all application objects within an ASP.NET application</param>
        public void Init(HttpApplication context)
        {
            this.context = context;
            context.BeginRequest += new EventHandler(this.Context_BeginRequest);
            context.EndRequest += new EventHandler(this.Context_EndRequest);
        }

        /// <summary>
        /// Handles the BeginRequest event of the Context control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void Context_BeginRequest(object sender, EventArgs e)
        {
            PDDataClassesDataContext.InvoiceInserted += new EventHandler<EntityInsertedEventArgs<Invoice>>(this.PDDataClassesDataContext_InvoiceInserted);
            PDDataClassesDataContext.TaskUpdated += new EventHandler<EntityUpdatedEventArgs<Task>>(this.PDDataClassesDataContext_TaskUpdated);
        }

        /// <summary>
        /// Handles the EndRequest event of the Context control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void Context_EndRequest(object sender, EventArgs e)
        {
            PDDataClassesDataContext.InvoiceInserted -= this.PDDataClassesDataContext_InvoiceInserted;
            PDDataClassesDataContext.TaskUpdated -= this.PDDataClassesDataContext_TaskUpdated;
        }

        /// <summary>
        /// Handles the InvoiceInserted event of the PDDataClassesDataContext control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="PD.DataAccess.EntityInsertedEventArgs&lt;PD.DataAccess.Invoice&gt;"/> instance containing the event data.</param>
        private void PDDataClassesDataContext_InvoiceInserted(object sender, EntityInsertedEventArgs<Invoice> e)
        {
            WorkflowRuntime workflowRuntime = PDWorkflowRuntimeUtility.CurrentWorkflowRuntime;
            if (workflowRuntime != null)
            {
                Dictionary<string, object> parameters = new Dictionary<string, object>();
                parameters.Add(TrackDataKeys.Issuer, HttpContext.Current.ProviderUserKey());
                parameters.Add(TrackDataKeys.InvolvedAccountant, e.InsertedEntity.InvolvedAccountant);
                parameters.Add(TrackDataKeys.InvolvedSupervisor, e.InsertedEntity.InvolvedSupervisor);
                parameters.Add(TrackDataKeys.InvoiceId, e.InsertedEntity.InvoiceId);

                WorkflowInstanceHelper instance = this.CreateAndRunWorkflow(workflowRuntime, e.InsertedEntity.InvoiceId, parameters);

                Logger.Instance.Info(
                    string.Format(
                        CultureInfo.InvariantCulture,
                        instance.Success ? Messages.WorkflowCreatedSuccessfully : Messages.WorkflowCreationFailed,
                        instance.Success ? new object[] { instance.InstanceId, e.InsertedEntity.InvoiceId } : new object[] { e.InsertedEntity.InvoiceId }));

                return;
            }

            Logger.Instance.Info(Messages.NullWorkflowRuntime);
        }

        /// <summary>
        /// Handles the TaskUpdated event of the PDDataClassesDataContext control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="PD.DataAccess.EntityUpdatedEventArgs&lt;PD.DataAccess.Task&gt;"/> instance containing the event data.</param>
        private void PDDataClassesDataContext_TaskUpdated(object sender, EntityUpdatedEventArgs<Task> e)
        {
            WorkflowRuntime workflowRuntime = PDWorkflowRuntimeUtility.CurrentWorkflowRuntime;
            if (workflowRuntime != null)
            {
                InvoiceState state = e.After.Invoice.WorkflowTrackingState;
                this.TryRaiseInvoiceCorrected(workflowRuntime, e.After.Invoice.WorkflowInstance.WorkflowInstanceId, e.After.TaskId, state);
                this.TryRaiseInvoiceConfirmed(workflowRuntime, e.After.Invoice.WorkflowInstance.WorkflowInstanceId, e.After.TaskId, state);
                this.TryRaiseInvoiceApprovedByChiefAccountant(workflowRuntime, e.After.Invoice.WorkflowInstance.WorkflowInstanceId, e.After.TaskId, state);
                this.TryRaiseInvoiceApprovedByCEO(workflowRuntime, e.After.Invoice.WorkflowInstance.WorkflowInstanceId, e.After.TaskId, state);
                this.TryRaiseInvoiceFinallyApproved(workflowRuntime, e.After.Invoice.WorkflowInstance.WorkflowInstanceId, e.After.TaskId, state);
                this.TryRaiseInvoiceArchived(workflowRuntime, e.After.Invoice.WorkflowInstance.WorkflowInstanceId, e.After.TaskId, state);
                return;
            }

            Logger.Instance.Info(Messages.NullWorkflowRuntime);
        }

        /// <summary>
        /// Tries the raise invoice corrected event.
        /// </summary>
        /// <param name="workflowRuntime">The workflow runtime.</param>
        /// <param name="instanceId">The instance id.</param>
        /// <param name="taskId">The task id.</param>
        /// <param name="state">The state.</param>
        private void TryRaiseInvoiceCorrected(WorkflowRuntime workflowRuntime, Guid instanceId, int taskId, InvoiceState state)
        {
            try
            {
                if (InvoiceState.InvoiceIncorrectAndWaitingForCorrectInfo == state)
                {
                    InvoiceProcessingService eventService = workflowRuntime.GetService<InvoiceProcessingService>();
                    eventService.RaiseInvoiceCorrected(taskId, instanceId);
                    this.RunWorkflow(workflowRuntime, instanceId);
                }
            }
            catch (EventDeliveryFailedException ex)
            {
                Logger.Instance.Error(ex.Message, ex);
            }
        }

        /// <summary>
        /// Tries the raise invoice confirmed.
        /// </summary>
        /// <param name="workflowRuntime">The workflow runtime.</param>
        /// <param name="instanceId">The instance id.</param>
        /// <param name="taskId">The task id.</param>
        /// <param name="state">The state.</param>
        private void TryRaiseInvoiceConfirmed(WorkflowRuntime workflowRuntime, Guid instanceId, int taskId, InvoiceState state)
        {
            try
            {
                if (InvoiceState.ConfirmTaskAdded == state)
                {
                    InvoiceProcessingService eventService = workflowRuntime.GetService<InvoiceProcessingService>();
                    eventService.RaiseInvoiceConfirmed(taskId, instanceId);
                    this.RunWorkflow(workflowRuntime, instanceId);
                }
            }
            catch (EventDeliveryFailedException ex)
            {
                Logger.Instance.Error(ex.Message, ex);
            }
        }

        /// <summary>
        /// Tries the raise invoice approved by chief accountant.
        /// </summary>
        /// <param name="workflowRuntime">The workflow runtime.</param>
        /// <param name="instanceId">The instance id.</param>
        /// <param name="taskId">The task id.</param>
        /// <param name="state">The state.</param>
        private void TryRaiseInvoiceApprovedByChiefAccountant(WorkflowRuntime workflowRuntime, Guid instanceId, int taskId, InvoiceState state)
        {
            try
            {
                if (InvoiceState.AwaitingForAprovalByChiefAccountant == state)
                {
                    InvoiceProcessingService eventService = workflowRuntime.GetService<InvoiceProcessingService>();
                    eventService.RaiseInvoiceApprovedByChiefAccountant(taskId, instanceId);
                    this.RunWorkflow(workflowRuntime, instanceId);
                }
            }
            catch (EventDeliveryFailedException ex)
            {
                Logger.Instance.Error(ex.Message, ex);
            }
        }

        /// <summary>
        /// Tries the raise invoice approved by CEO.
        /// </summary>
        /// <param name="workflowRuntime">The workflow runtime.</param>
        /// <param name="instanceId">The instance id.</param>
        /// <param name="taskId">The task id.</param>
        /// <param name="state">The state.</param>
        private void TryRaiseInvoiceApprovedByCEO(WorkflowRuntime workflowRuntime, Guid instanceId, int taskId, InvoiceState state)
        {
            try
            {
                if (InvoiceState.AwaitingForAprovalByCEO == state)
                {
                    InvoiceProcessingService eventService = workflowRuntime.GetService<InvoiceProcessingService>();
                    eventService.RaiseInvoiceApprovedByCEO(taskId, instanceId);
                    this.RunWorkflow(workflowRuntime, instanceId);
                }
            }
            catch (EventDeliveryFailedException ex)
            {
                Logger.Instance.Error(ex.Message, ex);
            }
        }

        /// <summary>
        /// Tries the raise invoice finally approved.
        /// </summary>
        /// <param name="workflowRuntime">The workflow runtime.</param>
        /// <param name="instanceId">The instance id.</param>
        /// <param name="taskId">The task id.</param>
        /// <param name="state">The state.</param>
        private void TryRaiseInvoiceFinallyApproved(WorkflowRuntime workflowRuntime, Guid instanceId, int taskId, InvoiceState state)
        {
            try
            {
                if (InvoiceState.AwaitingForFinalAproval == state)
                {
                    InvoiceProcessingService eventService = workflowRuntime.GetService<InvoiceProcessingService>();
                    eventService.RaiseInvoiceFinallyApproved(taskId, instanceId);
                    this.RunWorkflow(workflowRuntime, instanceId);
                }
            }
            catch (EventDeliveryFailedException ex)
            {
                Logger.Instance.Error(ex.Message, ex);
            }
        }

        /// <summary>
        /// Tries the raise invoice archived.
        /// </summary>
        /// <param name="workflowRuntime">The workflow runtime.</param>
        /// <param name="instanceId">The instance id.</param>
        /// <param name="taskId">The task id.</param>
        /// <param name="state">The state.</param>
        private void TryRaiseInvoiceArchived(WorkflowRuntime workflowRuntime, Guid instanceId, int taskId, InvoiceState state)
        {
            try
            {
                if (InvoiceState.AwaitingForArchived == state)
                {
                    InvoiceProcessingService eventService = workflowRuntime.GetService<InvoiceProcessingService>();
                    eventService.RaiseInvoiceArchived(taskId, instanceId);
                    this.RunWorkflow(workflowRuntime, instanceId);
                }
            }
            catch (EventDeliveryFailedException ex)
            {
                Logger.Instance.Error(ex.Message, ex);
            }
        }

        #region Helpers

        /// <summary>
        /// Creates the and run workflow.
        /// </summary>
        /// <param name="workflowRuntime">The workflow runtime.</param>
        /// <param name="invoiceId">The invoice id.</param>
        /// <param name="parameters">The parameters.</param>
        /// <returns>
        /// The workflow instance.
        /// </returns>
        private WorkflowInstanceHelper CreateAndRunWorkflow(WorkflowRuntime workflowRuntime, int invoiceId, Dictionary<string, object> parameters)
        {
            WorkflowInstanceHelper instance = new WorkflowInstanceHelper();

            try
            {
                instance.CreateAndRunWorkflow<InvoiceProcessingWorkflow>(workflowRuntime, parameters);
                if (instance.Success)
                {
                    this.UpdateWorkflowInstanceInternalId(invoiceId, instance.InstanceId);
                }
            }
            catch (Exception ex)
            {
                if (ex is InvalidOperationException ||
                    ex is ArgumentException)
                {
                    Logger.Instance.Error(ex.Message, ex);
                }
                else
                {
                    throw;
                }
            }

            return instance;
        }

        /// <summary>
        /// Runs the workflow.
        /// </summary>
        /// <param name="workflowRuntime">The workflow runtime.</param>
        /// <param name="instanceId">The instance id.</param>
        /// <returns>
        /// The workflow instance.
        /// </returns>
        private WorkflowInstanceHelper RunWorkflow(WorkflowRuntime workflowRuntime, Guid instanceId)
        {
            WorkflowInstanceHelper instance = new WorkflowInstanceHelper();

            try
            {
                instance.RunWorkflow(workflowRuntime, instanceId);
            }
            catch (Exception ex)
            {
                if (ex is InvalidOperationException ||
                    ex is ArgumentException)
                {
                    Logger.Instance.Error(ex.Message, ex);
                }
                else
                {
                    throw;
                }
            }

            return instance;
        }

        /// <summary>
        /// Updates the workflow instance internal id.
        /// </summary>
        /// <param name="invoiceId">The invoice id.</param>
        /// <param name="instanceId">The instance id.</param>
        private void UpdateWorkflowInstanceInternalId(int invoiceId, Guid instanceId)
        {
            using (PDDataClassesDataContext context = new PDDataClassesDataContext())
            {
                context.EventFiringEnabled = false;
                PD.DataAccess.WorkflowInstance workflowInstance = context.WorkflowInstances.FirstOrDefault(x => x.WorkflowInstanceId == instanceId);
                PD.DataAccess.Invoice invoice = context.Invoices.FirstOrDefault(x => x.InvoiceId == invoiceId);
                if (workflowInstance != null && invoice != null)
                {
                    invoice.WorkflowInstanceInternalId = workflowInstance.WorkflowInstanceInternalId;
                    context.SubmitChanges();
                }
            }
        }

        #endregion
    }
}
